/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.inspur.edp.web.designschema.generator;

import com.inspur.edp.cef.designtime.api.IGspCommonField;
import com.inspur.edp.cef.designtime.api.collection.GspAssociationCollection;
import com.inspur.edp.cef.designtime.api.collection.GspEnumValueCollection;
import com.inspur.edp.cef.designtime.api.element.GspAssociation;
import com.inspur.edp.cef.designtime.api.element.GspElementDataType;
import com.inspur.edp.cef.designtime.api.element.GspElementObjectType;
import com.inspur.edp.das.commonmodel.IGspCommonElement;
import com.inspur.edp.formserver.viewmodel.GspViewModelElement;
import com.inspur.edp.lcm.metadata.api.entity.GspMetadata;
import com.inspur.edp.metadata.rtcustomization.api.CustomizationService;
import com.inspur.edp.udt.designtime.api.entity.SimpleDataTypeDef;
import com.inspur.edp.udt.designtime.api.entity.UnifiedDataTypeDef;
import com.inspur.edp.udt.designtime.api.nocode.BusinessField;
import com.inspur.edp.udt.designtime.api.nocode.IBusinessFieldService;
import com.inspur.edp.web.common.customexception.WebCustomException;
import com.inspur.edp.web.common.metadata.MetadataGetterParameter;
import com.inspur.edp.web.common.metadata.MetadataTypeEnum;
import com.inspur.edp.web.common.metadata.MetadataUtility;
import com.inspur.edp.web.common.utility.ResourceLocalizeUtil;
import com.inspur.edp.web.common.utility.StringUtility;
import com.inspur.edp.web.designschema.constant.I18nExceptionConstant;
import com.inspur.edp.web.designschema.constant.I18nMsgConstant;
import io.iec.edp.caf.commons.utils.SpringBeanUtils;
import org.apache.commons.lang3.ObjectUtils;

import java.util.HashMap;

/**
 * 类型构造上下文参数
 *
 * @author noah
 */
public class TypeBuildingContext {
    private boolean isRuntime;

    public boolean isRuntime() {
        return isRuntime;
    }

    public void setRuntime(boolean runtime) {
        isRuntime = runtime;
    }

    private GspElementObjectType ObjectType = GspElementObjectType.None;

    public final GspElementObjectType getObjectType() {
        return ObjectType;
    }

    public final void setObjectType(GspElementObjectType value) {
        ObjectType = value;
    }

    private GspEnumValueCollection Enums;

    public final GspEnumValueCollection getEnums() {
        return Enums;
    }

    public final void setEnums(GspEnumValueCollection value) {
        Enums = value;
    }

    private GspAssociationCollection Associations;

    public final GspAssociationCollection getAssociations() {
        return Associations;
    }

    public final void setAssociations(GspAssociationCollection value) {
        Associations = value;
    }

    private String UnifiedDataType;

    /**
     * 增加此参数的目的是为了适应零代码 传递对应的实例形式
     */
    private UnifiedDataTypeDef unifiedDataTypeDefInstance;

    public UnifiedDataTypeDef getUnifiedDataTypeDefInstance() {
        return unifiedDataTypeDefInstance;
    }

    public void setUnifiedDataTypeDefInstance(UnifiedDataTypeDef unifiedDataTypeDefInstance) {
        this.unifiedDataTypeDefInstance = unifiedDataTypeDefInstance;
    }

    private BusinessField businessFieldInstance;

    public BusinessField getBusinessFieldInstance() {
        return businessFieldInstance;
    }

    public TypeBuildingContext setBusinessFieldInstance(BusinessField businessFieldInstance) {
        this.businessFieldInstance = businessFieldInstance;
        return this;
    }

    public String getRefElementId() {
        return refElementId;
    }

    public void setRefElementId(String refElementId) {
        this.refElementId = refElementId;
    }

    private String refElementId;

    public String getBusinessFieldId() {
        return businessFieldId;
    }

    public void setBusinessFieldId(String businessFieldId) {
        this.businessFieldId = businessFieldId;
    }

    private String businessFieldId;

    public final String getUnifiedDataType() {
        return UnifiedDataType;
    }

    public final void setUnifiedDataType(String value) {
        UnifiedDataType = value;
    }

    public final boolean getHasUnifiedDataType() {
        return !StringUtility.isNullOrEmpty(this.getUnifiedDataType());
    }

    public final boolean getHasBusiFieldId() {
        return !StringUtility.isNullOrEmpty(this.getBusinessFieldId());
    }

    private boolean isRefElement;

    public boolean getIsRefElement() {
        return isRefElement;
    }

    public void setIsRefElement(boolean isRefElement) {
        this.isRefElement = isRefElement;
    }

    public final boolean getInUnifedDataType() {
        return (getParent() != null && (getParent().getHasUnifiedDataType() || getParent().getInUnifedDataType()));
    }

    public final boolean getHasAssociation() {
        return getObjectType() == GspElementObjectType.Association && getAssociations() != null && getAssociations().size() > 0;
    }

    public final boolean getIsMixinUDTAssociation() {
        return getHasAssociation() && getHasUnifiedDataType();
    }

    public final boolean getIsDynamicField() {
        return getObjectType() == GspElementObjectType.DynamicProp;
    }

    public final boolean getIsMultiLanguageField() {
        return this.<Boolean>Get("MultiLanguageInput", Boolean.class);
    }

    private boolean HasPresetValue;

    public final boolean getHasPresetValue() {
        return HasPresetValue;
    }

    public final void setHasPresetValue(boolean value) {
        HasPresetValue = value;
    }

    private GspElementDataType DataType;

    public final GspElementDataType getDataType() {
        return DataType;
    }

    public final void setDataType(GspElementDataType value) {
        DataType = value;
    }

    private HashMap<String, Object> Params = new HashMap<>();

    public final HashMap<String, Object> getParams() {
        return Params;
    }

    public final void setParams(HashMap<String, Object> value) {
        Params = value;
    }

    public final <T> T Get(String key, Class<T> cls) {
        if (getParams().containsKey(key)) {
            Object value = getParams().get(key);
            return (T) value;
        } else {
            //N转J N版代码 此处通过类型判断赋予初始值兼容Nbadefault(T)
            //return default(T);
            if (cls == Boolean.class) {
                return (T) Boolean.FALSE;
            } else if (cls == Character.class) {
                return (T) Character.valueOf((char) 0);
            } else if (cls == Byte.class) {
                return (T) Byte.valueOf((byte) 0);
            } else if (cls == Short.class) {
                return (T) Short.valueOf((short) 0);
            } else if (cls == Integer.class) {
                return (T) Integer.valueOf(0);
            } else if (cls == Long.class) {
                return (T) Long.valueOf(0);
            } else if (cls == Float.class) {
                return (T) Float.valueOf(0);
            } else if (cls == Double.class) {
                return (T) Double.valueOf(0.0);
            } else {
                return null;
            }
        }
    }

    public HashMap<String, Integer> TypeNameMaps = new HashMap<>();

    public HashMap<String, Integer> ElementIdMaps = new HashMap<>();

    private TypeBuildingContext Root;

    public final TypeBuildingContext getRoot() {
        return Root;
    }

    public final void setRoot(TypeBuildingContext value) {
        Root = value;
    }

    private TypeBuildingContext Parent;

    public final TypeBuildingContext getParent() {
        return Parent;
    }

    public final void setParent(TypeBuildingContext value) {
        Parent = value;
    }

    private boolean HasReviseTypeName(String revisedTypeName) {
        HashMap<String, Integer> typeNameMaps = this.getRoot() != null ? this.getRoot().TypeNameMaps : TypeNameMaps;
        return typeNameMaps.containsKey(revisedTypeName);
    }

    private void AddRevisedTypeName(String revisedTypeName) {
        HashMap<String, Integer> typeNameMaps = this.getRoot() != null ? this.getRoot().TypeNameMaps : TypeNameMaps;
        if (!typeNameMaps.containsKey(revisedTypeName)) {
            typeNameMaps.put(revisedTypeName, 0);
        } else {
            typeNameMaps.put(revisedTypeName, typeNameMaps.get(revisedTypeName) + 1);
        }
    }

    private String GenerateNewRevisedTypeName(String revisedTypeName) {
        HashMap<String, Integer> typeNameMaps = this.getRoot() != null ? this.getRoot().TypeNameMaps : TypeNameMaps;
        return String.format("%1$s$%2$s", revisedTypeName, typeNameMaps.get(revisedTypeName));
    }

    public final String ReviseTypeName(String suffixFieldId, String typeName) {
        String suffix = suffixFieldId.substring(0, 4);
        String revisedTypeName = String.format("%1$s%2$s", typeName, suffix.replace(suffix.substring(0, 1), suffix.substring(0, 1).toUpperCase()));
        if (!HasReviseTypeName(revisedTypeName)) {
            AddRevisedTypeName(revisedTypeName);
            return revisedTypeName;
        } else {
            AddRevisedTypeName(revisedTypeName);
            return GenerateNewRevisedTypeName(revisedTypeName);
        }
    }

    private boolean HasReviseElementId(String elementId) {
        HashMap<String, Integer> elementIdMaps = this.getRoot() != null ? this.getRoot().ElementIdMaps : ElementIdMaps;
        return elementIdMaps.containsKey(elementId);
    }

    private void AddRevisedElementId(String elementId) {
        HashMap<String, Integer> elementIdMaps = this.getRoot() != null ? this.getRoot().ElementIdMaps : ElementIdMaps;
        if (!elementIdMaps.containsKey(elementId)) {
            elementIdMaps.put(elementId, 0);
        } else {
            elementIdMaps.put(elementId, elementIdMaps.get(elementId) + 1);
        }
    }

    private String GenerateNewRevisedElementId(String elementId) {
        HashMap<String, Integer> elementIdMaps = this.getRoot() != null ? this.getRoot().ElementIdMaps : ElementIdMaps;
        return String.format("%1$s$%2$s", elementId.substring(0, elementId.length() - 2), elementIdMaps.get(elementId));
    }

    public final String ReviseElementId(String elementId) {
        String revisedElementId = elementId;
        if (this.getParent() != null && this.getInUnifedDataType()) {
            String parentId = this.getParent().Get("Id", String.class);
            revisedElementId = String.format("%1$s%2$s", parentId.substring(0, 8), elementId.substring(8, 36));
        }
        if (!HasReviseElementId(revisedElementId)) {
            AddRevisedElementId(revisedElementId);
            return revisedElementId;
        } else {
            AddRevisedElementId(revisedElementId);
            return GenerateNewRevisedElementId(revisedElementId);
        }
    }


    public static TypeBuildingContext Create(SimpleDataTypeDef element, TypeBuildingContext parent) {
        TypeBuildingContext context = new TypeBuildingContext();
        context.setObjectType(element.getObjectType());
        context.setEnums(element.getContainEnumValues());
        context.setAssociations(element.getChildAssociations());
        context.setDataType(element.getMDataType());

        HashMap<String, Object> params = new HashMap<>();
        params.put("Id", (element.getId() != null) ? element.getId() : "");
        params.put("Code", (element.getCode() != null) ? element.getCode() : "");
        params.put("Name", (element.getName() != null) ? element.getName() : "");
        params.put("Label", (element.getCode() != null) ? element.getCode() : "");
        params.put("BindingField", (element.getCode() != null) ? element.getCode() : "");
        params.put("Path", (element.getCode() != null) ? element.getCode() : "");
        params.put("Require", parent != null ? (parent.Get("Require", Boolean.class) || element.getIsRequired()) : element.getIsRequired());
        params.put("Readonly", false);
        params.put("DefaultValue", (element.getDefaultValue() != null) ? element.getDefaultValue() : "");
        params.put("Length", element.getLength());
        params.put("Precision", element.getPrecision());
        params.put("MultiLanguageInput", false);
        params.put("IsBigNumber", parent != null ? parent.<Boolean>Get("IsBigNumber", Boolean.class) : false);
        params.put("BelongObjectCode", parent != null ? parent.Get("BelongObjectCode", String.class) : "");
//        FormUdtExtension formUdtExtension = (FormUdtExtension) element.getUdtExtensions().get("Form");
//        params.put("ExtendProperty", formUdtExtension);

        context.setParams(params);
        context.setRoot(parent != null && parent.getRoot() != null ? parent.getRoot() : parent);
        context.setRuntime(parent != null && parent.isRuntime());
        context.setParent(parent);
        if (parent != null && parent.getIsMixinUDTAssociation()) {
            for (GspAssociation association : parent.getAssociations()) {
                for (IGspCommonField refElement : association.getRefElementCollection()) {
                    if (!refElement.getIsFromAssoUdt()) {
                        context.getAssociations().get(0).getRefElementCollection().add(refElement);
                    }
                }
            }
        }
        return context;
    }


    public static TypeBuildingContext Create(GspViewModelElement element, TypeBuildingContext parent, boolean isRuntime) {
        verifyFieldElement(element);

        TypeBuildingContext tempVar = new TypeBuildingContext();
        tempVar.setObjectType(element.getObjectType());
        tempVar.setEnums(element.getContainEnumValues());
        tempVar.setAssociations(element.getChildAssociations());
        tempVar.setDataType(element.getMDataType());
        tempVar.setUnifiedDataType(element.getUdtID());
        tempVar.setIsRefElement(element.getIsRefElement());
        tempVar.setBusinessFieldId(element.getRefBusinessFieldId());

        HashMap<String, Object> params = new HashMap<>();
        params.put("Id", (element.getID() != null) ? element.getID() : "");
        params.put("Code", (element.getCode() != null) ? element.getCode() : "");
        params.put("Name", (element.getName() != null) ? element.getName() : "");
        params.put("Label", (element.getLabelID() != null) ? element.getLabelID() : "");
        params.put("BindingField", (element.getLabelID() != null) ? element.getLabelID() : "");
        params.put("Path", (element.getLabelID() != null) ? element.getLabelID() : "");
        params.put("Require", element.getIsRequire());
        params.put("Readonly", ((element instanceof IGspCommonElement) ? element : null) != null && ((IGspCommonElement) ((element instanceof IGspCommonElement) ? element : null)).getReadonly());
        params.put("DefaultValue", (element.getDefaultValue() != null) ? element.getDefaultValue() : "");
        params.put("Length", element.getLength());
        params.put("Precision", element.getPrecision());
        params.put("MultiLanguageInput", element.getIsMultiLanguage() && element.isEnableMultiLanguageInput());
        params.put("IsBigNumber", element.isBigNumber());
        params.put("Element", element);
        tempVar.setParams(params);
        tempVar.setRoot(parent != null && parent.getRoot() != null ? parent.getRoot() : parent);
        tempVar.setRuntime(parent != null ? parent.isRuntime() : isRuntime);
        tempVar.setParent(parent);
        return tempVar;
    }

    /**
     * 构造参数类型为IGspCommonField 的Context
     *
     * @param element IGspCommonField
     * @param parent
     * @return
     */
    public static TypeBuildingContext Create(IGspCommonField element, TypeBuildingContext parent) {
        verifyFieldElement(element);

        TypeBuildingContext tempVar = new TypeBuildingContext();
        tempVar.setObjectType(element.getObjectType());
        tempVar.setEnums(element.getContainEnumValues());
        tempVar.setAssociations(element.getChildAssociations());
        tempVar.setDataType(element.getMDataType());
        tempVar.setUnifiedDataType(element.getUdtID());
        tempVar.setRefElementId(element.getRefElementId());

        HashMap<String, Object> params = new HashMap<>();
        params.put("Id", (element.getID() != null) ? element.getID() : "");
        params.put("Code", (element.getCode() != null) ? element.getCode() : "");
        params.put("Name", (element.getName() != null) ? element.getName() : "");
        params.put("Label", (element.getLabelID() != null) ? element.getLabelID() : "");
        params.put("BindingField", (element.getLabelID() != null) ? element.getLabelID() : "");
        params.put("Path", (element.getLabelID() != null) ? element.getLabelID() : "");
        params.put("Require", element.getIsRequire());
        boolean readonly = false;
        if (element instanceof IGspCommonElement) {
            readonly = ((IGspCommonElement) element).getReadonly();
        }
        params.put("Readonly", readonly);
        params.put("DefaultValue", (element.getDefaultValue() != null) ? element.getDefaultValue() : "");
        params.put("Length", element.getLength());
        params.put("Precision", element.getPrecision());
        params.put("MultiLanguageInput", false);
        params.put("IsBigNumber", element.isBigNumber());
        tempVar.setParams(params);
        tempVar.setRoot(parent != null && parent.getRoot() != null ? parent.getRoot() : parent);
        tempVar.setRuntime(parent != null && parent.isRuntime());
        tempVar.setParent(parent);
        return tempVar;
    }


    public static TypeBuildingContext CreateSimpleTypeContextFromAssociation(TypeBuildingContext context, TypeBuildingContext parent) {
        GspAssociationCollection associations = context.getAssociations();
        if (associations == null || associations.size() == 0) {
            throw new WebCustomException(I18nExceptionConstant.WEB_DESIGN_SCHEMA_ERROR_0006, new String[]{context.Get("Name", String.class)});
        }

        String tempVar = context.Get("Label", String.class);
        String label = (tempVar != null) ? tempVar : "";

        String camelLable = StringUtility.toCamelCase(label);

        TypeBuildingContext tempVar2 = new TypeBuildingContext();
        tempVar2.setDataType(context.getDataType());
        tempVar2.setHasPresetValue(true);

        HashMap<String, Object> params = new HashMap<>();
        params.put("Id", ObjectUtils.firstNonNull(associations.get(0).getId(), ""));
        params.put("Code", ObjectUtils.firstNonNull(context.Get("Code", String.class), ""));
        params.put("Name", ObjectUtils.firstNonNull(context.Get("Name", String.class), ""));
        params.put("Label", label);
        params.put("BindingField", label);
        params.put("BindingPath", !StringUtility.isNullOrEmpty(camelLable) ? camelLable + "." + camelLable : "");
        params.put("Path", !StringUtility.isNullOrEmpty(label) ? label + "." + label : "");
        params.put("Require", context.<Boolean>Get("Require", Boolean.class));
        params.put("Readonly", context.<Boolean>Get("Readonly", Boolean.class));
        params.put("DefaultValue", ObjectUtils.firstNonNull(context.Get("DefaultValue", String.class), ""));
        params.put("Length", context.<Integer>Get("Length", Integer.class));
        params.put("Precision", context.<Integer>Get("Precision", Integer.class));
        params.put("MultiLanguageInput", false);
        params.put("IsBigNumber", context.<Boolean>Get("IsBigNumber", Boolean.class));
        tempVar2.setParams(params);
        tempVar2.setRoot(parent != null && parent.getRoot() != null ? parent.getRoot() : parent);
        tempVar2.setRuntime(parent != null && parent.isRuntime());
        tempVar2.setParent(parent);
        return tempVar2;
    }

    private static void verifyFieldElement(IGspCommonField element) {
        VerifyUnifiedDataType(element);
    }

    private static void VerifyUnifiedDataType(IGspCommonField element) {
        if (element.getIsUdt()) {
            if (StringUtility.isNullOrEmpty(element.getUdtID().trim())) {
                throw new WebCustomException(I18nExceptionConstant.WEB_DESIGN_SCHEMA_ERROR_0007, new String[]{element.getID(), element.getLabelID(), element.getName()});
            }
        }
    }

    private UnifiedDataTypeDef loadUnifiedDataTypeMetadata() {
        String uri = this.getUnifiedDataType();
        UnifiedDataTypeDef unifiedDataType;
        GspMetadata typeMetadata = null;
        if (this.isRuntime()) {
            CustomizationService customizationService = SpringBeanUtils.getBean(CustomizationService.class);
            typeMetadata = customizationService.getMetadata(uri);
        } else {
            MetadataGetterParameter metadataGetterParameter = MetadataGetterParameter.getNewInstance(uri, null, MetadataTypeEnum.ViewModel);
            metadataGetterParameter.setTargetMetadataNotFoundMessage(ResourceLocalizeUtil.getString(I18nMsgConstant.WEB_DESIGN_SCHEMA_MSG_0004, uri));
            typeMetadata = MetadataUtility.getInstance().getMetadataWithDesign(metadataGetterParameter);
        }
        if (typeMetadata == null) {
            throw new WebCustomException(I18nExceptionConstant.WEB_DESIGN_SCHEMA_ERROR_0005, new String[]{uri});
        }
        unifiedDataType = (UnifiedDataTypeDef) ((typeMetadata.getContent() instanceof UnifiedDataTypeDef) ? typeMetadata.getContent() : null);
        this.setUnifiedDataTypeDefInstance(unifiedDataType);
        return unifiedDataType;
    }

    private BusinessField loadBusinessFieldMetadata() {
        String businessFieldId = this.getBusinessFieldId();

        IBusinessFieldService businessFieldService = SpringBeanUtils.getBean(IBusinessFieldService.class);
        BusinessField businessField = businessFieldService.getBusinessField(businessFieldId);
        this.setBusinessFieldInstance(businessField);
        return businessField;
    }
}
